//#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <error.h>
#include "err.h"
#include "runner.h"
#include "parser.h"
#include "str_utils.h"
#include "utils.h"
#include "runner_common.h"

void init_runner()
{
   //current_dir = getcwd(NULL, maxstrlen);
   //history = fopen(HISTORY_NAME, "a+");
  // file_pos = ftell
}

static err_strc runner_check_cmdl(prun_strc data)
{
    if (data == NULL || data->argv == NULL)
        return make_error_struct(SHELL_PARSE_ERROR, 0, "Invalid parsing");
    if (!check_delimeters(data->argv[0]))
        return make_error_struct(SHELL_PARSE_ERROR, 0, "%s", data->argv[0]);

    err_strc res = init_no_err();
    if (data->argc == 0)
        return res;

    int8_t prev_csc_chk = is_change_stream(data->argv[0]);
    for (int i = 1; i < data->argc; i++)
    {
        if (!check_delimeters(data->argv[i]))
            return make_error_struct(SHELL_PARSE_ERROR, 0,  "%s", data->argv[i]);
        int8_t csc_chk = is_change_stream(data->argv[i]);
        if (prev_csc_chk && (csc_chk || is_emptystr(data->argv[i])))
        {
            res =  make_error_struct(SHELL_PARSE_ERROR, 0, ERRP_INVALID_TOKEN, data->argv[i]);
            return res;
        }
        prev_csc_chk = csc_chk;
    }

    if (prev_csc_chk)
        res =  make_error_struct(SHELL_PARSE_ERROR, 0,  ERRP_INVALID_TOKEN, data->argv[data->argc - 1]);
    return res;
}

static  inline int my_open(char *pathname, int flags, mode_t mode)
{
#ifdef X64
    return open64(pathname, flags, mode)
#else
    return open(pathname, flags, mode);
#endif
}


static err_strc runner_open_stream(char *pathname, int stream_num, int append)
{
    int flags = 0;
    mode_t mode = 0;
    err_strc res = init_no_err();
    switch(stream_num)
    {
    case STDIN_FILENO: flags = O_RDONLY; mode = S_IRUSR | S_IRGRP | S_IROTH; break;
    case STDOUT_FILENO: flags |= O_SYNC;
    case STDERR_FILENO: flags |= O_WRONLY | O_CREAT;
        mode = S_IRUSR |  S_IWUSR | S_IWGRP | S_IWOTH;
    }

    if (append)
        flags |= O_APPEND;
    else
        flags |= O_TRUNC;
    int file_descriptor = my_open(pathname, flags, mode);
    if (file_descriptor == -1)
        res = make_error_struct(LIBC_OPEN_ERROR, 0, 0, "%s",  pathname);
    else
    {
        dup2(file_descriptor, stream_num);
        close(file_descriptor);
    }
    return res;
}

static err_strc runner_open_streams(char *sep, char *pathname)
{

    if (strncmp(sep, "<", 1) == 0)
        return runner_open_stream(pathname, STDIN_FILENO, 0);

    if (strncmp(sep, ">", 1) == 0)
        return runner_open_stream(pathname,STDOUT_FILENO, 0);
    if (strncmp(sep, "&>", 2) == 0)
        return runner_open_stream(pathname, STDERR_FILENO, 0);

    if (strncmp(sep, ">>", 2) == 0)
        return runner_open_stream(pathname,STDOUT_FILENO, 1);
    if (strncmp(sep, "&>>", 3) == 0)
        return runner_open_stream(pathname, STDERR_FILENO, 1);

    return make_error_struct(SHELL_NO_ERROR, 0, 0, "");
}


static err_strc runner_prepare_run_binary(prun_strc data)
{
    err_strc res = init_no_err();
    int i = 0;
    while (i < data->argc)
    {
        int indicator = 0;
        if ((i + 1 < data->argc) && is_change_stream (data->argv[i]))
        {
            res = runner_open_streams(data->argv[i], data->argv[i + 1]);
            if (res.errnum)
                return res;
            indicator = 1;
        }

        if (indicator)
        {
            free(data->argv[i]);
            free(data->argv[i + 1]);
            int j = i;
            for (; (j + 2 < data->argc) && (data->argv[j + 2] != NULL); j++)
                data->argv[j] = data->argv[j + 2];
            free(data->argv[j]);
            data->argv[j] = NULL;

        }
        else
            i++;
    }
    return res;
}


err_strc runner_run_cd(prun_strc data)
{
    char *str;
    int8_t alloc_ind = 0;
    if (data->argc < 2 || is_emptystr(data->argv[1]))
    {
        alloc_ind = 0;
        str = get_env_variable(HOME_VARIABLE_NAME);
    }
    else
        str = data->argv[1];

    err_strc res = init_no_err();
    res.errnum = chdir(str);

    update_env_dir();

    if (res.errnum)
       res = make_error_struct(errno, SHELL_SYSTEM, "");

    if (alloc_ind)
        free(str);
    return res;
}

#define RE_FOREGROUND 1 << 0
#define RE_PIPELINE 1 << 1



rn_req runner_run_binary(prun_strc data, int8_t attrib)
{
    rn_req res;
    res.pid = -1;
    res.error.errmsg = NULL;
    res.error.errnum = 0;
    int pid_res;
    res.error = runner_check_cmdl(data);

    if (res.error.errnum != 0)
        return res;

    if (is_exit_case(data))
    {
        res.error = make_error_struct(SHELL_QUIT_ERROR, 0, "");
        return res;
    }

    if (is_cd_case(data))
    {
        res.error =  runner_run_cd(data);
        return res;
    }

    if (is_help_case(data))
    {
        res.error = make_error_struct(SHELL_NO_ERROR, 0, "");
        print_help();
        return res;
    }

    if (!(res.pid = fork()))
    {
        res.error = runner_prepare_run_binary(data);
        if (res.error.errnum != 0)
            print_error(res.error);

        execvp(data->argv[0], data->argv);
        if (!attrib & RE_FOREGROUND)
            print_error(make_error_struct(SHELL_CANT_RUN_ERR, SHELL_FORKED_ERROR, ERRP_CANT_RUN, data->argv[0]));
        else
            print_error(make_error_struct(SHELL_KILL_ERR, SHELL_FORKED_ERROR, "%ld", getpid()));
    }
    else
        if (! attrib & RE_FOREGROUND)
            wait(&pid_res);

    if (attrib & RE_PIPELINE)
    {
       // res.fdes[0] = dup2
    }
    res.pid = pid_res;
    return res;
}


err_strc run_file(char *filename)
{
    FILE *in = fopen(filename, "r");
    vchar str;
    vchar_init(&str);
    err_strc res = init_no_err();
    while (!feof(in) && !res.errnum)
    {
        int c;
        str.len = 0;
        while (!feof(in) && (c = fgetc(in)) != '\n')
            vchar_append(&str, c);
        res = run_iteration(str.str);
    }
    fclose(in);
    vchar_free(&str);
    return res;
}


err_strc run_iteration(char *c_str)
{
    pprs_struct data = read_and_parse(c_str);
    size_t i = 0;
    run_strc current, prev;
    run_strc_init(&current);
    run_strc_init(&prev);
    rn_req req;
    req.pid = 0;
    req.error = init_no_err();
    int8_t runner_was_pipeline = 0;
    while (i < data->argc)
    {
        int8_t chk_cond = 0;
        current.argc = 0;
        while (i < data->argc &&
               (chk_cond = get_run_chk_cond(data->argv[i])) == NO_CONDITION)
        {
            run_strc_add(&current, data->argv[i]);
            i++;
        }
        int8_t run_attrib = 0;
        if (i != data->argc)
        {
            if (chk_cond != NO_CONDITION)
            {
                if (chk_cond != PIPELINE_CONDITION)
                {
                    run_attrib = (chk_cond != FOREGROUND_CONDITION);
                    req = runner_run_binary(&current, run_attrib);
                    if (req.error.errnum != 0
                            && chk_cond == AND_CONDITION)
                    {

                        if (data->iseof)
                            req.error = make_quit_error();
                        run_strc_free(&current);
                        run_strc_free(&prev);
                        prs_struct_free(data);
                        return req.error;
                    }
                    else
                        print_error(req.error);
                }
                else
                {
                    run_attrib |= RE_PIPELINE | RE_FOREGROUND;

                    req = runner_run_binary(&current, run_attrib);
                    runner_was_pipeline = 1;
                    if (req.error.errnum)
                        print_error(req.error);
                    else
                    {
                        req.error = make_error_struct(SHELL_STUB_ERR, 0, "Pipeline STUB");
                        run_strc_free(&prev);
                        run_strc_free(&current);
                        prs_struct_free(data);
                        return req.error;
                    }
                }
                prev = run_strc_copy(&current);
             }
            i++;
        }
    }

    if (i == data->argc &&  i && current.argc)
        req = runner_run_binary(&current, 0);

    if (data->iseof)
        req.error = make_quit_error();
    run_strc_free(&prev);
    run_strc_free(&current);
    prs_struct_free(data);
    return req.error;
}
